﻿/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX.Direct3D;

namespace Daidalos.Management
{
    /// <summary>
    /// 
    /// </summary>
    public sealed class GraphicalConsole : Daidalos.Patterns.SingletonManager<GraphicalConsole>
    {
        #region Nested Structs
        /// <summary>
        ///     Enumerator containing all variable types supported by the console
        /// </summary>
        public enum ConsoleItemType
        {
            TYPE_UCHAR,
            TYPE_CHAR,
            TYPE_UINT,
            TYPE_INT,
            TYPE_FLOAT,
            TYPE_DOUBLE,
            TYPE_STRING,
            TYPE_VECTOR,
        }

        /// <summary>
        ///     Enumerator containing all method types supported by the console
        /// </summary>
        public enum ConsoleMethodType
        {
            TYPE_VOID,
            TYPE_ARGS,
            TYPE_INF_ARGS,
        }

        /// <summary>
        ///     Structure for a variable in the console
        /// </summary>
        public struct ConsoleItem
        {
            /// <summary>
            ///     The variables name
            /// </summary>
            public string name;

            /// <summary>
            ///     The variable's type
            /// </summary>
            public ConsoleItemType type;

            /// <summary>
            ///     The variable's data
            /// </summary>
            public object data;
        }

        /// <summary>
        /// 
        /// </summary>
        public struct ConsoleMethod
        {
            /// <summary>
            ///     Delegate for a method with arguments
            /// </summary>
            public console_method_args method_args;

            /// <summary>
            ///     Delegate for a method without arguments
            /// </summary>
            public console_method_void method_void;

            /// <summary>
            ///     This method's type
            /// </summary>
            public ConsoleMethodType type;

            /// <summary>
            ///     If it's a method with limited argument count,
            ///     num_args stores the number of needed arguments
            /// </summary>
            public int num_args;
        }
        #endregion

        #region Members
        /// <summary>
        ///     A delegate for methods without arguments
        /// </summary>
        /// <param name="args"></param>
        public delegate void console_method_void();

        /// <summary>
        ///     A delegate for methods with arguments
        /// </summary>
        /// <param name="args"></param>
        public delegate void console_method_args(string[] args);

        /// <summary>
        ///     Our commandbuffer. Stores the recent logged activities
        /// </summary>
        private static List<string> commandBuffer = new List<string>();

        /// <summary>
        ///     The commandhistory for shell-like iterating 
        ///     through past commands
        /// </summary>
        private List<string> history = new List<string>();

        /// <summary>
        ///     A hashmap containing all registered variables
        /// </summary>
        private Dictionary<string, ConsoleItem> itemList = new Dictionary<string, ConsoleItem>();

        /// <summary>
        ///     A hashmap containing all registered methods
        /// </summary>
        private Dictionary<string, ConsoleMethod> methodList = new Dictionary<string, ConsoleMethod>();

        /// <summary>
        ///     The currently typed in command line.
        /// </summary>
        private string textBuffer = "";

        /// <summary>
        ///     Borderthickness
        /// </summary>
        private int border = 3;

        /// <summary>
        ///     The console's font
        /// </summary>
        private Daidalos.Font.BitmapString font;

        /// <summary>
        ///     Flag that determines if the console is active or not
        /// </summary>
        private bool active = false;

        /// <summary>
        ///     The console's transparency [0, 250]
        /// </summary>
        private int alpha = 220;
        #endregion

        #region Properties
        /// <summary>
        ///     
        /// </summary>
        public static List<string> CommandBuffer
        {
            get
            {
                return commandBuffer;
            }
            set
            {
                commandBuffer = value;
            }
        }

        /// <summary>
        ///     List of previously computed commandlines
        /// </summary>
        public List<string> History
        {
            get
            {
                return history;
            }
            set
            {
                history = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public Dictionary<string, ConsoleItem> ItemList
        {
            get
            {
                return itemList;
            }
            set
            {
                itemList = value;
            }
        }

        /// <summary>
        ///     Read/Write currently typed in command line.
        /// </summary>
        public string TextBuffer
        {
            get
            {
                return textBuffer;
            }
            set
            {
                textBuffer = value;
            }
        }

        /// <summary>
        ///     Read/Write border's thickness
        /// </summary>
        public int Border
        {
            get
            {
                return border;
            }
            set
            {
                border = value;
            }
        }

        /// <summary>
        ///     Read/Write activity state flag
        /// </summary>
        public bool Active
        {
            get
            {
                return active;
            }
            set
            {
                active = value;
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        ///     Constructor. Initializes all needed things and registers built-in variables
        ///     and methods for basic functionality.
        /// </summary>
        public GraphicalConsole()
        {
            Log("Initializing console...");

            // create the fontshader and initialize the font
            Shaders.FontShaderSet fontshader = new Shaders.FontShaderSet();
            fontshader.ScreenWidth = Graphics.RenderDevice.Instance.ScreenWidth;
            fontshader.ScreenHeight = Graphics.RenderDevice.Instance.ScreenHeight;
            fontshader.XOffset = 0;
            fontshader.YOffset = 0;
            Log("  + Loading font...");
            ResourceManager.Instance.RegisterShader("FontShader", fontshader);
            font = new Daidalos.Font.BitmapString("Console", "arial", 1.0f, System.Drawing.Color.WhiteSmoke);
            font.PrepareOnce();
            font.X = 0;
            font.Y = 0;

            // register built-in variables
            Log("  + Registering variables...");
            RegisterItem("/alpha", alpha, ConsoleItemType.TYPE_INT);

            // register built-in methods
            Log("  + Registering methods...");
            RegisterMethod("/hide", Hide);
            RegisterMethod("/help", Help);
            RegisterMethod("/?", Help);
            RegisterMethod("/list_variables", Variables);
            RegisterMethod("/list_methods", Methods);
            RegisterMethod("/title", SetTitle);
            RegisterMethod("/exit", Daidalos.Graphics.RenderDevice.Instance.RequestTermination);
            Log("Console activated");
        }
        #endregion

        #region Methods
        /// <summary>
        ///     Activates the console
        /// </summary>
        public void Show()
        {
            Active = true;
        }

        /// <summary>
        ///     Deactivates the console
        /// </summary>
        public void Hide()
        {
            Active = false;
        }

        /// <summary>
        ///     Toggles active/inactive
        /// </summary>
        public void Toggle()
        {
            Active = !Active;
        }

        /// <summary>
        ///     Logs a message to the console
        /// </summary>
        /// <param name="message">
        ///     The message
        /// </param>
        public void Log(string message)
        {
            CommandBuffer.Add(message);
        }

        /// <summary>
        ///     Logs a message to the console
        /// </summary>
        /// <param name="message">
        ///     The message
        /// </param>
        public void Log(object message)
        {
            CommandBuffer.Add(message.ToString());
        }

        /// <summary>
        ///     Returns the data of a registered item
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public object GetItem(string name)
        {
            return ItemList[name].data;
        }

        /// <summary>
        ///     Displays the consolewindow
        /// </summary>
        public void Display()
        {
            // if the console is not active, then why render the shit?
            if (!Active)
                return;

            CustomVertex.TransformedColoredTextured[] v = new CustomVertex.TransformedColoredTextured[6];
            Graphics.Texture texture = Management.ResourceManager.Instance.GetTextureByName("console.png");

            // Fetch coordinates.
            float left = 0;
            float top = 0;
            float bottom = Daidalos.Graphics.RenderDevice.Instance.ScreenHeight / 2.0f;
            float right = Daidalos.Graphics.RenderDevice.Instance.ScreenWidth; 

            // Border
            v[0].X = left; v[0].Y = top; v[0].Tu = 0.0f; v[0].Tv = 0.0f;
            v[2].X = left; v[2].Y = bottom; v[2].Tu = 0.0f; v[2].Tv = 1.0f;
            v[1].X = right; v[1].Y = bottom; v[1].Tu = 1.0f; v[1].Tv = 1.0f;

            v[3] = v[0];
            v[4].X = right; v[4].Y = top; v[4].Tu = 1.0f; v[4].Tv = 0.0f;
            v[5] = v[1]; 

            System.Drawing.Color bg = System.Drawing.Color.Gray;
            bg = System.Drawing.Color.FromArgb((int)(GetItem("/alpha")), bg.R, bg.G, bg.B);
            for (int i = 0; i < 6; ++i) { v[i].Z = 1; v[i].Rhw = 1; v[i].Color = bg.ToArgb(); }


            // render it!

            // Create vertex buffer.
            VertexBuffer b = new VertexBuffer(
                typeof(CustomVertex.TransformedColoredTextured),
                6,
                Graphics.RenderDevice.Instance.Device,
                Usage.WriteOnly,
                CustomVertex.TransformedColoredTextured.Format,
                Pool.Default);

            Microsoft.DirectX.GraphicsStream s = b.Lock(0, 0, 0);
            s.Write(v);
            b.Unlock();

            texture.Bind(0);
            Graphics.RenderDevice.Instance.Device.VertexFormat = CustomVertex.TransformedColoredTextured.Format;
            Graphics.RenderDevice.Instance.Device.SetStreamSource(0, b, 0);
            Graphics.RenderDevice.Instance.Device.DrawPrimitives(PrimitiveType.TriangleList, 0, 2);


            // prepare to render the text
            Shaders.ShaderSet shader = ResourceManager.Instance.GetShaderByName("FontShader");
            ((Shaders.FontShaderSet)shader).XOffset = Border;
            ((Shaders.FontShaderSet)shader).YOffset = 20;


            font.Text = "> ";

            // render it!
            foreach (string tmp in CommandBuffer)
                font.Text += tmp + "\n> ";
            font.Text += TextBuffer + "|";
            ((Shaders.FontShaderSet)shader).Begin();
            font.PrepareOnce();
            font.Display();
            ((Shaders.FontShaderSet)shader).End();

            if(CommandBuffer.Count * 11.5f > bottom)
                CommandBuffer.RemoveAt(0);
        }

        /// <summary>
        ///     Registers a new variable at the console
        /// </summary>
        /// <param name="name">
        ///     The variable's name
        /// </param>
        /// <param name="data">
        ///     A pointer to the data
        /// </param>
        /// <param name="type">
        ///     Specifies the type, e.g. if it's an int, a string, or anything else
        /// </param>
        public void RegisterItem(string name, object data, ConsoleItemType type)
        {
            ConsoleItem item = new ConsoleItem();
            item.name = name;
            item.type = type;
            item.data = data;
            ItemList[name] = item;
        }

        /// <summary>
        ///     Registers a new method with zero parameters at the console
        /// </summary>
        /// <param name="name">
        ///     The method's name
        /// </param>
        /// <param name="method">
        ///     The method itself
        /// </param>
        public void RegisterMethod(string name, console_method_void method)
        {
            ConsoleMethod item = new ConsoleMethod();
            item.method_void = method;
            item.type = ConsoleMethodType.TYPE_VOID;
            methodList[name] = item;
        }

        /// <summary>
        ///     Registers a new methid with an array of string arguments 
        ///     that can be infinite and has no restrictions
        /// </summary>
        /// <param name="name">
        ///     The method's name
        /// </param>
        /// <param name="method">
        ///     The method itself
        /// </param>
        public void RegisterMethod(string name, console_method_args method)
        {
            ConsoleMethod item = new ConsoleMethod();
            item.method_args = method;
            item.type = ConsoleMethodType.TYPE_INF_ARGS;
            methodList[name] = item;
        }

        /// <summary>
        ///     Registers a method with a limited string array at the console.
        ///     This array MUST have a specified number of arguments.
        /// </summary>
        /// <param name="name">
        ///     The method's name
        /// </param>
        /// <param name="method">
        ///     The method itself
        /// </param>
        /// <param name="args">
        ///     The number of arguments this method should be invoked with
        /// </param>
        public void RegisterMethod(string name, console_method_args method, int args)
        {
            ConsoleMethod item = new ConsoleMethod();
            item.method_args = method;
            item.type = ConsoleMethodType.TYPE_ARGS;
            item.num_args = args;
            methodList[name] = item;
        }

        /// <summary>
        ///     Hm, opposite of RegisterItem?
        /// </summary>
        /// <param name="name">
        ///     The name of the item that is to be remvoed from the list
        /// </param>
        public void RemoveItem(string name)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        public void PassKey(char key)
        {
            // if it's not active, we don't have to input new keys
            if (!Active)
                return;

            if (key == 0)
                return;

            if (key == 13)
            {
                ParseCommandLine();
                TextBuffer = "";
                return;
            }

            if (key == 8)
            {
                if (TextBuffer.Length == 0)
                    return;
                TextBuffer = TextBuffer.Substring(0, TextBuffer.Length - 1);
                return;
            }

            TextBuffer += key;
        }

        /// <summary>
        ///     Computes the current typed commandline and it's parameters
        /// </summary>
        /// <returns>
        ///     True if commandline was correct and everything went fine
        /// </returns>
        private bool ParseCommandLine()
        {
            // if console's not active, we don't have to worry about input
            if (!Active)
                return false;

            // compute all arguments and store them in a vector
            int index = 0;
            List<string> arguments = new List<string>();

            CommandBuffer.Add(TextBuffer);

            string[] tmp = TextBuffer.Split(' ');

            foreach (string s in tmp)
                arguments.Add(s);

            // loop thorugh all items and look them up in the hashmap.
            // if one of them occurs, we check for 3 cases:
            //   1) no paramaters. that means we wanna output the value
            //   2) parameters that match the variable, so we set the variable to the given value
            //   3) too much parameters or wrong parameters, so we output an error and do nothing
            foreach(KeyValuePair<string, ConsoleItem> item in ItemList)
            {
                if(item.Key == arguments[0])
                {
                    switch(item.Value.type)
                    {
                        // found an integer
                        case ConsoleItemType.TYPE_INT:
                            {
                                if (arguments.Count > 2)
                                    return false;
                                // read
                                else if (arguments.Count == 1)
                                {
                                    Log(item.Value.name + " = " + ((int)item.Value.data));
                                    return true;
                                }
                                // write
                                else if (arguments.Count == 2)
                                {
                                    ConsoleItem i = item.Value;
                                    try
                                    {
                                        i.data = (int)int.Parse(arguments[1]);
                                    }
                                    catch (System.Exception)
                                    {
                                        LogManager.Instance.Log("Invalid argument for parameter 1");
                                    }
                                    ItemList[item.Key] = i;
                                    return true;
                                } break;
                            }
                        // found a string
                        case ConsoleItemType.TYPE_STRING:
                            {
                                // read
                                if (arguments.Count == 1)
                                {
                                    LogManager.Instance.Log(((string)item.Value.data));
                                }
                                // write
                                else
                                {
                                    ConsoleItem i = item.Value;
                                    string s = "";
                                    for (int k = 1; k < arguments.Count; ++k)
                                        s += arguments[k] + " ";
                                    i.data = s;
                                    itemList[item.Key] = i;
                                }
                            } break;
                    }
                }
            }

            // loop through all methods and look them up in the hashmap.
            // if we find one, we check if all parameters are set correctly
            // and invoke the method.
            foreach (KeyValuePair<string, ConsoleMethod> method in methodList)
            {
                if (arguments[0] == method.Key)
                {
                    switch (method.Value.type)
                    {
                        case ConsoleMethodType.TYPE_VOID:
                            {
                                method.Value.method_void.Invoke();
                                return true;
                            }
                        // found a method with limited arguments
                        case ConsoleMethodType.TYPE_ARGS:
                            {
                                if (method.Value.num_args != (arguments.Count - 1))
                                {
                                    LogManager.Instance.Log("Error: Wrong number of arguments. Are you missing something?");
                                    return false;
                                }
                                method.Value.method_args(arguments.ToArray());
                                return true;
                            } 
                        // found a method with infinite arguments
                        case ConsoleMethodType.TYPE_INF_ARGS:
                            {
                                method.Value.method_args(arguments.ToArray());
                                return true;
                            } 
                    }
                }
            }

            LogManager.Instance.Log("Error: Unknown method or variable.");
            return true;
        }
        #endregion

        #region BuiltIn
        /// <summary>
        ///     Prints an overview of all built-in commands
        /// </summary>
        public void Help()
        {
            Log("Commands are:");
            Log("  /help - This message");
            Log("  /alpha value - Sets the transparency to \"value\"");
            Log("  /hide - Hides the console");
            Log("  /exit - Close the application");
            Log("  /title text - Sets the window's title to \"text\"");
            Log("  /list_methods - Lists all registered methods");
            Log("  /list_variables - Lists all registered variables");
        }

        /// <summary>
        ///     Lists all registered variables
        /// </summary>
        public void Variables()
        {
            foreach (KeyValuePair<string, ConsoleItem> item in ItemList)
                Log("  - " + item.Key);
        }

        /// <summary>
        ///     Lists all registered methods
        /// </summary>
        public void Methods()
        {
            foreach (KeyValuePair<string, ConsoleMethod> item in methodList)
                Log("  - " + item.Key);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args">
        /// </param>
        public void SetTitle(string[] args)
        {
            string tmp = "";

            for(int i = 1; i < args.Length; ++i)
                tmp += args[i] + " ";
            Graphics.RenderDevice.Instance.Window.Text = tmp;
        }
        #endregion
    }
}
